home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 November: Tool Chest / Dev.CD Nov 00 TC Disk 1.toast / Sample Code / Networking / OT Virtual Server / OTVirtualClient / OTVirtualClient.c next >
Encoding:
C/C++ Source or Header  |  2000-09-28  |  41.7 KB  |  1,659 lines  |  [TEXT/CWIE]

  1. /*
  2.     File:        OTVirtualClient.c
  3.  
  4.     Contains:    This is an OpenTransport sample client application which can be used 
  5.                 to exercise and test the OpenTransport Virtual Server sample.
  6.                 It also demonstrates coding techniques for OT client applications.
  7.     
  8.                 You are welcome to use this code in any way to create you own
  9.                 OpenTransport applications.   For more information on this program,
  10.                 please review the document "About OTVirtual Server".
  11.  
  12.     Written by: Eric Okholm    
  13.  
  14.     Copyright:    Copyright © 1999 by Apple Computer, Inc., All Rights Reserved.
  15.  
  16.                 You may incorporate this Apple sample source code into your program(s) without
  17.                 restriction. This Apple sample source code has been provided "AS IS" and the
  18.                 responsibility for its operation is yours. You are not permitted to redistribute
  19.                 this Apple sample source code as "Apple sample source code" after having made
  20.                 changes. If you're going to re-distribute the source, we require that you make
  21.                 it clear in the source that the code was descended from Apple sample source
  22.                 code, but that you've made changes.
  23.  
  24.     Change History (most recent first):
  25.                 7/22/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
  26.                 
  27.  
  28. */
  29.  
  30. #define DoAlert(x)            { sprintf(gProgramErr, x); gProgramState = kProgramError; }        
  31. #define DoAlert1(x, y)        { sprintf(gProgramErr, x, y); gProgramState = kProgramError; }        
  32. #define DoAlert2(x, y, z)    { sprintf(gProgramErr, x, y, z); gProgramState = kProgramError;}    
  33.  
  34. //
  35. //    Program mode
  36. //
  37. //    Before compiling, 
  38. //    set kDebugLevel to 0 for production
  39. //                    or 1 for debug code.
  40. //
  41. //    In production mode, the code attempts to recover cleanly from any problems in encounters.
  42. //    In debug mode, the unexplained phenomenon cause an alert box highlighting the situation
  43. //    to be delivered and then the program exits.
  44. //
  45.  
  46. #define kDebugLevel    1
  47.  
  48. #if kDebugLevel > 0
  49.  
  50. #define DBAlert(x)            DoAlert(x)        
  51. #define DBAlert1(x, y)        DoAlert1(x, y)        
  52. #define DBAlert2(x, y, z)    DoAlert2(x, y, z)    
  53.  
  54. #else
  55.  
  56. #define DBAlert(x)            { }
  57. #define DBAlert1(x, y)        { }
  58. #define DBAlert2(x, y, z)    { }
  59.  
  60. #endif
  61.  
  62. //
  63. //    Include files
  64. //
  65. #include <Dialogs.h>
  66. #include <Events.h>
  67. #include <Fonts.h>
  68. #include <GestaltEqu.h>
  69. #include <Memory.h>
  70. #include <Menus.h>
  71. #include <QuickDraw.h>
  72. #include <SegLoad.h>
  73. #include <stdio.h>
  74. #include <StdLib.h>
  75. #include <String.h>
  76. #include <strings.h>
  77. #include <ToolUtils.h>
  78. #include <Windows.h>
  79.  
  80. #include <OpenTptInternet.h>        // includes OpenTransport.h
  81. #include <OpenTptClient.h>            // needed for OTReleaseBuffer()
  82.  
  83. //
  84. //    Defines, enums, resource IDs
  85. //
  86. #define kInFront                    (WindowPtr) -1
  87. #define    kWindowResID                128
  88.  
  89.     // Apple Menu
  90. #define kAppleMenuResID                128
  91. #define    kAppleMenuAbout                1
  92.  
  93.     // File Menu
  94. #define kFileMenuResID                129
  95. #define kFileMenuOpen                1
  96. #define    kFileMenuClose                2
  97. #define    kFileMenuQuit                4
  98.  
  99.     // Edit Menu
  100. #define kEditMenuResID                130                // Edit menu is disabled
  101.  
  102.     // Client Menu
  103. #define kClientMenuResID            131
  104. #define kClientMenuTCPPrefs            1
  105.  
  106.     // Alerts, etc.
  107. #define kAlertExitResID                128
  108. #define kAboutBoxResID                130
  109.  
  110.     // TCP Prefs Dialog
  111. #define kTCPPrefsDlogResID            129
  112. #define    kServerAddrDItem            2
  113. #define kServerPortDItem            4
  114. #define    kMaxConnectionsDItem        6
  115. #define kStartStopDItem                7
  116.  
  117.     // Overall program states
  118. enum
  119. {
  120.     kProgramRunning        = 0,
  121.     kProgramDone        = 1,
  122.     kProgramError        = 2
  123. };
  124.  
  125.     // Server states
  126. enum
  127. {
  128.     kClientStopped        = 0,
  129.     kClientRunning        = 1,
  130.     kClientShuttingDown    = 2
  131. };
  132.  
  133.     // Bit numbers in EPInfo stateFlags fields
  134. enum
  135. {
  136.     kOpenInProgressBit                = 0
  137. };
  138.  
  139.     // Misc stuff
  140. enum
  141. {
  142.     kTimerIntervalInSeconds        = 3,
  143.     kTimerInterval                = (kTimerIntervalInSeconds * 1000),
  144.     kServerRequestSize            = 128,
  145.     kOTVersion111                = 0x01110000
  146. };
  147.  
  148.     // Endpoint Info Structure
  149.  
  150. struct EPInfo
  151. {
  152.     EndpointRef        erf;                //    actual endpoint
  153.     struct EPInfo*    next;                //    used to link all acceptor's EPInfos (not atomic)
  154.     OTLink            link;                //    link into an OT LIFO (atomic)
  155.     UInt8            stateFlags;            //    various status fields
  156. };
  157. typedef struct EPInfo EPInfo;
  158.  
  159. //
  160. //    Globals
  161. //
  162. EPInfo*                gDNS                        = NULL;
  163. EPInfo*             gConnectors                    = NULL;
  164. InetHostInfo        gServerHostInfo;
  165. Boolean                gWaitForServerAddr;
  166. int                    gClientState                = kClientStopped;
  167. int                    gProgramState                = kProgramRunning;
  168. char                gProgramErr[128];
  169. DialogPtr            gDialogPtr                    = NULL;
  170. WindowPtr            gWindowPtr                    = NULL;
  171. long                gSleepTicks                    = 60;
  172. Str255                gServerAddrStr                = "\p17.202.32.195";
  173. long                gServerAddr                    = 0;
  174. Str255                gServerPortStr                = "\p2001";
  175. long                gServerPort                    = 0;
  176. Str255                gMaxConnectionsStr            = "\p100";
  177. long                gMaxConnections                = 0;
  178. Boolean                gClientRunning                = false;
  179. Str255                gStartStr                    = "\pStart";
  180. Str255                gStopStr                    = "\pStop";
  181. SInt32                gCntrEndpts                    = 0;
  182. SInt32                gCntrIdleEPs                = 0;
  183. SInt32                gCntrBrokenEPs                = 0;
  184. SInt32                gCntrPending                = 0;
  185. SInt32                gCntrConnections            = 0;
  186. SInt32                gCntrTotalConnections        = 0;
  187. SInt32                gCntrBytesRcvd                = 0;
  188. SInt32                gCntrTotalBytesRcvd            = 0;
  189. SInt32                gCntrDiscon                    = 0;
  190. OTLIFO                gIdleEPLIFO;
  191. OTLIFO*                gIdleEPs                    = &gIdleEPLIFO;
  192. OTLIFO                gBrokenEPLIFO;
  193. OTLIFO*                gBrokenEPs                    = &gBrokenEPLIFO;
  194. OTConfiguration*    gCfgMaster                    = NULL;
  195. Boolean                gWaitForEventLoop            = false;
  196. long                gTimerTask                    = 0;
  197. SInt32                gCntrIntervalConnects        = 0;
  198. SInt32                gCntrIntervalBytes            = 0;
  199. SInt32                gConnectsPerSecond            = 0;
  200. SInt32                gConnectsPerSecondMax        = 0;
  201. SInt32                gKBytesPerSecond            = 0;
  202. SInt32                gKBytesPerSecondMax            = 0;
  203. SInt32                gCntrIntervalEventLoop        = 0;
  204. SInt32                gEventsPerSecond            = 1;
  205. SInt32                gEventsPerSecondMax            = 1;
  206. Boolean                gDoWindowUpdate                = true;
  207. unsigned char         gServerRequest[kServerRequestSize];
  208. OSType                gOTVersionSelector            = 'otvr';
  209. UInt32                gOTVersion;
  210.  
  211. //
  212. //    OpenTransport Networking Code Prototypes
  213. //
  214. static void            DoConnect(EPInfo*);
  215. static Boolean         EPClose(EPInfo*);
  216. static Boolean        EPOpen(EPInfo*, OTConfiguration* cfg);
  217. static void            NetEventLoop(void);
  218. static void            NetInit(void);
  219. static void            NetShutdown(void);
  220. static pascal void    Notifier(void*, OTEventCode, OTResult, void*);
  221. static void            ReadData(EPInfo*);
  222. static void            Recycle(void);
  223. static void         SendRequest(EPInfo*);
  224. static void         StartClient(void);
  225. static void         StopClient(void);
  226. static void            TimerInit();
  227. static void            TimerDestroy();
  228. static pascal void    TimerRun(void*);
  229.  
  230. //
  231. //    Macintosh Program Wrapper Prototypes 
  232. //
  233. static void            AboutBox(void);
  234. static void            AlertExit(char* );
  235. static void            DialogClose(void);
  236. static Boolean         EventDialog(EventRecord*);
  237. static void            EventDrag(WindowPtr, Point);
  238. static void            EventGoAway(WindowPtr, Point);
  239. static void            EventKeyDown(EventRecord*);
  240. static void            EventLoop(void);
  241. static void            EventMouseDown(EventRecord*);
  242. static void         MacInit(void);
  243. static void         MacInitROM(void);
  244. static void            MyC2PStr(char*, Str255);
  245. static void            MyP2CStr(Str255, char*);
  246. static void         MenuDispatch(long);
  247. static void         SetupMenus(void);
  248. static void         TCPPrefsDialog(void);
  249. static void            TCPPrefsReset(void);
  250. static void         WindowClose(void);
  251. static void         WindowOpen(void);
  252. static void            WindowUpdate(void);
  253.  
  254.  
  255. //////////////////////////////////////////////////////////////////////////////////////
  256. //
  257. //    OpenTransport Networking Code
  258. //
  259. //    The code in this section provides the networking portions of the 
  260. //    OpenTransport Virtual Client.
  261. //
  262. //////////////////////////////////////////////////////////////////////////////////////
  263.  
  264. //
  265. //    DoBind
  266. //
  267. //    This routine requests a wildcard port binding from the transport protocol.
  268. //    Since the program doesn't care what port is returned, it passes in NULL
  269. //    for the bind return parameter.  The bind request structure is ephemeral
  270. //    and can be a local stack variable since OT is done with it when the call returns.
  271. //    The bind is done when the notifier receives a T_BINDCOMPLETE event.
  272. //
  273. static void DoBind(EPInfo* epi)
  274. {
  275.     OSStatus err;
  276.     TBind bindReq;
  277.     InetAddress    inAddr;
  278.     
  279.     //
  280.     //    Bind the endpoint to a wildcard address 
  281.     //    (assign us a port, we don't care which one).
  282.     //
  283.     OTInitInetAddress(&inAddr, 0, 0);
  284.     bindReq.addr.len = sizeof(InetAddress);
  285.     bindReq.addr.buf = (unsigned char*) &inAddr;
  286.     bindReq.qlen = 0;
  287.     
  288.     err = OTBind(epi->erf, &bindReq, NULL);
  289.     if (err != kOTNoError)
  290.     {
  291.         DBAlert1("DoBind: OTBind error %d", err);
  292.         return;
  293.     }
  294. }
  295.  
  296. //
  297. //    DoConnect
  298. //
  299. //    This routine attempts establish a new connection to the globally known
  300. //    server address and port.   If the program is still trying to use the
  301. //    DNR to resolve the server's host name into an IP address, the endpoint
  302. //    is queued for later connection.   
  303. //
  304. static void DoConnect(EPInfo* epi)
  305. {
  306.     OSStatus err;
  307.     TCall sndCall;
  308.     InetAddress inAddr;
  309.     OTLink* link;
  310.     
  311.     //    Don't want new connections if already shutting down.
  312.     if (gProgramState != kProgramRunning || gClientState != kClientRunning)
  313.         return;
  314.         
  315.     if (gWaitForServerAddr || gWaitForEventLoop)
  316.     {
  317.         if (epi != NULL)
  318.         {
  319.             OTLIFOEnqueue(gIdleEPs, &epi->link);
  320.             OTAtomicAdd32(1, &gCntrIdleEPs);
  321.         }
  322.         return;
  323.     }
  324.         
  325.     //    If we weren't passed a specific EPInfo, try to get an idle one.
  326.     if (epi == NULL)
  327.     {
  328.         link = OTLIFODequeue(gIdleEPs);
  329.         if (link == NULL)
  330.             return;
  331.         OTAtomicAdd32(-1, &gCntrIdleEPs);
  332.         epi = OTGetLinkObject(link, EPInfo, link);
  333.     }
  334.  
  335.     OTInitInetAddress(&inAddr, gServerPort, gServerAddr);
  336.     OTMemzero(&sndCall, sizeof(TCall));
  337.     sndCall.addr.len     = sizeof(InetAddress);                
  338.     sndCall.addr.buf    = (unsigned char*) &inAddr;
  339.     
  340.     OTAtomicAdd32(1, &gCntrPending);
  341.     err = OTConnect(epi->erf, &sndCall, NULL);
  342.     if (err != kOTNoDataErr)
  343.     {
  344.         OTAtomicAdd32(-1, &gCntrPending);
  345.         DBAlert2("DoConnect: OTConnect error %d state %d", err, OTGetEndpointState(epi->erf));
  346.         return;
  347.     }
  348.     
  349.     //
  350.     //    If OTConnect didn't return an error, this thread will 
  351.     //    resume when the notifier gets a T_CONNECT event...
  352.     //
  353. }
  354.  
  355. //
  356. //    EPClose
  357. //
  358. //    This routine is a front end to OTCloseProvider.   
  359. //    Centralizing closing of endpoints makes debugging and instrumentation easier.  
  360. //
  361. static Boolean EPClose(EPInfo* epi)
  362. {
  363.     OSStatus err;
  364.     
  365.     //
  366.     //    If an endpoint is still being opened, we can't close it yet.
  367.     //    There is no way to cancel an OTAsyncOpenEndpoint, so we just
  368.     //    have to wait for the T_OPENCOMPLETE event at the notifier.
  369.     //
  370.     if ( OTAtomicTestBit(&epi->stateFlags, kOpenInProgressBit) )
  371.         return false;
  372.         
  373.     err = OTCloseProvider(epi->erf);
  374.     epi->erf = NULL;
  375.     if (err)
  376.         DBAlert1("EPClose: OTCloseProvider error %d", err);
  377.  
  378.     if (epi != gDNS)
  379.         OTAtomicAdd32(-1, &gCntrEndpts);
  380.     return true;
  381.     
  382. }
  383.  
  384. //
  385. //    EPOpen:
  386. //
  387. //    A front end to OTAsyncOpenEndpoint.
  388. //    A status bit is set so we know there is an open in progress.
  389. //    It is cleared when the notifier gets a T_OPENCOMPLETE where the context
  390. //    pointer is this EPInfo.  Until that happens, this EPInfo can't be cleaned
  391. //    up and released.
  392. //
  393. static Boolean EPOpen(EPInfo* epi, OTConfiguration* cfg)
  394. {
  395.     OSStatus err;
  396.     
  397.     OTAtomicSetBit(&epi->stateFlags, kOpenInProgressBit);
  398.     err = OTAsyncOpenEndpoint(cfg, 0, NULL, &Notifier, epi);
  399.     if (err != kOTNoError)
  400.     {
  401.         OTAtomicClearBit(&epi->stateFlags, kOpenInProgressBit);
  402.         DBAlert1("EPOpen: OTAsyncOpenEndpoint error %d", err);
  403.         return false;
  404.     }
  405.     return true;
  406. }
  407.  
  408. //
  409. //    NetEventLoop
  410. //
  411. //    This routine is called once during each pass through the program's event loop.
  412. //    If the program is running on OT 1.1.2 or an earlier release, this is where
  413. //    outbound orderly releases are started (see comments in DoSndOrderlyRelease 
  414. //    for more information on that).   This is also where endpoints are "fixed" by
  415. //    closing them and opening a new one to replace them.   This is rarely necessary,
  416. //    but works around some timing issues in OTUnbind().  Having passed through the 
  417. //    event loop once, we assume it is safe to turn off throttle-back.  And, finally,
  418. //    if we have deferred handing of a T_LISTEN, here we start it up again.
  419. //
  420. static void NetEventLoop()
  421. {
  422.     Recycle();
  423.     gWaitForEventLoop = false;
  424.     DoConnect(NULL);
  425. }
  426.  
  427. //
  428. //    NetInit:
  429. //
  430. //    This is nothing but a front end to InitOpenTransport.
  431. //    The only reason for having this routine is to get the call to InitOpenTransport
  432. //    up into the "networking" section of the program and out of the 
  433. //    "macintosh program wrapper" section of the program.
  434. //
  435. static void NetInit()
  436. {
  437.     OSStatus err;
  438.     
  439.     err = InitOpenTransport();
  440.     if (err)
  441.     {
  442.         DBAlert1("NetInit: InitOpenTransport error %d", err);
  443.         return;
  444.     }
  445.     err = Gestalt(gOTVersionSelector, (long*) &gOTVersion);
  446.     if (err || (gOTVersion < kOTVersion111))
  447.     {
  448.         DoAlert("Please install Open Transport 1.1.1 or later");
  449.         return;
  450.     }
  451.     TimerInit();
  452. }
  453.  
  454. //
  455. //    NetShutdown:
  456. //
  457. //    Ditto...
  458. //
  459. static void NetShutdown()
  460. {
  461.     TimerDestroy();
  462.     CloseOpenTransport();
  463. }    
  464.  
  465.  
  466. //
  467. //    Notifier:
  468. //
  469. //    Most of the interesting networking code in this program resides inside 
  470. //    this notifier.   In order to run asynchronously and as fast as possible,
  471. //    things are done inside the notifier whenever possible.  Since almost
  472. //    everything is done inside the notifier, there was little need for specical
  473. //    synchronization code.
  474. //
  475. //    Note: The DNR events are combined with normal endpoint events in this notifier.
  476. //    The only events which are expected from the DNR are T_DNRSTRINGTOADDRCOMPLETE
  477. //    and T_OPENCOMPLETE.
  478. //
  479. //    IMPORTANT NOTE:  Normal events defined by XTI (T_LISTEN, T_CONNECT, etc)
  480. //    and OT completion events (T_OPENCOMPLETE, T_BINDCOMPLETE, etc.) are not
  481. //    reentrant.  That is, whenever our notifier is invoked with such an event,
  482. //    the notifier will not be called again by OT for another normal or completion
  483. //    event until we have returned out of the notifier - even if we make OT calls
  484. //    from inside the notifier.   This is a useful synchronization tool.
  485. //    However, there are two kinds of events which will cause the notifier to 
  486. //    be reentered.   One is T_MEMORYRELEASED, which always happens instantly.
  487. //    The other are state change events like kOTProviderWillClose.
  488. //
  489.  
  490. static pascal void Notifier(void* context, OTEventCode event, OTResult result, void* cookie)
  491. {
  492.     OSStatus err;
  493.     OTResult epState;
  494.     EPInfo* epi = (EPInfo*) context;
  495.  
  496.     //
  497.     //    Once the program is shutting down, most events would be uninteresting.
  498.     //    However, we still need T_OPENCOMPLETE and T_MEMORYRELEASED events since
  499.     //    we can't call CloseOpenTransport until all OTAsyncOpenEndpoints and
  500.     //    OTSends with AckSends have completed.   So those specific events
  501.     //    are still accepted.
  502.     //
  503.     if (gProgramState != kProgramRunning)
  504.     {
  505.         if (event != T_OPENCOMPLETE)
  506.             return;
  507.     }
  508.     
  509.     //
  510.     //    This really isn't necessary, it's just a sanity check which should be removed
  511.     //    once a program is debugged.   It's just making sure we don't get event notifications
  512.     //    after all of our endpoints have been closed.
  513.     //
  514.     if (gClientState == kClientStopped)
  515.     {
  516.         DBAlert1("Notifier: got event %d when client not running!", event);
  517.         return;
  518.     }
  519.     
  520.     //
  521.     //    Within the notifier, all action is based on the event code.
  522.     //    In this notifier, fatal errors all break out of the switch to the bottom.
  523.     //    As long as everything goes as expected, the case returns rather than breaks.
  524.     //
  525.     switch (event)
  526.     {
  527.         //
  528.         //    T_BINDCOMPLETE:
  529.         //
  530.         //    This event is returned when an endpoint has been bound to a wildcard addr.
  531.         //    No errors are expected.   The program immediately attempts to establish
  532.         //    a connection from this endpoint to the server.
  533.         //
  534.         case T_BINDCOMPLETE:
  535.         {
  536.             if (result != kOTNoError)
  537.             {
  538.                 DBAlert1("Notifier: T_BINDCOMPLETE result %d", result);
  539.                 return;
  540.             }
  541.             DoConnect(epi);            // resumes at T_CONNECT
  542.             return;
  543.         }
  544.         
  545.         //
  546.         //    T_CONNECT:
  547.         //
  548.         //    This event is returned when a connection is established to the server.
  549.         //    The program must call OTRcvConnect() to get the conenction information
  550.         //    and clear the T_CONNECT event from the stream.  Since OTRcvConnect()
  551.         //    returns immediately (rather than via a completion event to the notifier)
  552.         //    we can use local stack structures for parameters.
  553.         //
  554.         case T_CONNECT:
  555.         {
  556.             TCall call;
  557.             InetAddress caddr;
  558.             
  559.             if (result != kOTNoError)
  560.             {
  561.                 DBAlert1("Notifier: T_CONNECT result %d", result);
  562.                 return;
  563.             }
  564.             
  565.             call.addr.maxlen = sizeof(InetAddress);
  566.             call.addr.buf = (unsigned char*) &caddr;
  567.             call.opt.maxlen = 0;
  568.             call.opt.buf = NULL;
  569.             call.udata.maxlen = 0;
  570.             call.udata.buf = NULL;
  571.             
  572.             err = OTRcvConnect(epi->erf, &call);
  573.             if (err != kOTNoError)
  574.             {
  575.                 DBAlert1("Notifier: T_CONNECT - OTRcvConnect err %d", err);
  576.                 return;
  577.             }
  578.             OTAtomicAdd32(-1, &gCntrPending);
  579.             OTAtomicAdd32(1, &gCntrConnections);
  580.             OTAtomicAdd32(1, &gCntrTotalConnections);
  581.             OTAtomicAdd32(1, &gCntrIntervalConnects);
  582.             
  583.             SendRequest(epi);
  584.  
  585.             //
  586.             //    Since we won't be sending any data, 
  587.             //    might as well send along the orderly release now.
  588.             //
  589.             err = OTSndOrderlyDisconnect(epi->erf);
  590.             if (err != kOTNoError)
  591.             {
  592.                 if (err != kOTLookErr)
  593.                 {
  594.                     DBAlert1("Notifier: T_CONNECT: OTSndOrderlyDisconnect error %d", err);
  595.                 }
  596.             }
  597.             return;                // Wait for a T_DATA...
  598.         }
  599.         
  600.         //
  601.         //    T_DATA:
  602.         //
  603.         //    The main rule for processing T_DATA's is to remember that once you have
  604.         //    a T_DATA, you won't get another one until you have read to a kOTNoDataErr.
  605.         //    The advanced rule is to remember that you could get another T_DATA
  606.         //    during an OTRcv() which will eventually return kOTNoDataErr, presenting
  607.         //    the application with a synchronization issue to be most careful about.
  608.         //    
  609.         //    In this application, since an OTRcv() calls are made from inside the notifier,
  610.         //    this particular synchronization issue doesn't become a problem.
  611.         //
  612.         case T_DATA:
  613.         {
  614.             ReadData(epi);
  615.             return;
  616.         }
  617.         
  618.         //
  619.         //    T_DNRSTRINGTOADDRCOMPLETE:
  620.         //
  621.         //    This event occurs when the DNR has finished an attempt to translate
  622.         //    the server's name into an IP address we can use to connect to.
  623.         //
  624.         case T_DNRSTRINGTOADDRCOMPLETE:
  625.         {
  626.             if (result != kOTNoError)
  627.             {
  628.                 DBAlert1("Notifier: T_DNRSTRINGTOADDRCOMPLETE result %d", result);
  629.                 return;
  630.             }
  631.             gServerAddr = gServerHostInfo.addrs[0];
  632.             gWaitForServerAddr = false;
  633.             return;
  634.         }
  635.                 
  636.         //
  637.         //    T_DISCONNECT:
  638.         //
  639.         //    An inbound T_DISCONNECT event usually indicates that the other side of the
  640.         //    connection did an abortive disconnect (as opposed to an orderly release).
  641.         //    It also can be generated by the transport provider on the system (e.g. tcp)
  642.         //    when it decides that a connection is no longer in existance.
  643.         //
  644.         //    We receive the disconnect, but this program ignores the associated reason (NULL param).
  645.         //    It is possible to get back a kOTNoDisconnectErr from the OTRcvDisconnect call.
  646.         //    This can happen when either (1) the disconnect on the stream is hidden by a 
  647.         //    higher priority message, or (2) something has flushed or reset the disconnect
  648.         //    event in the meantime.   This is not fatal, and the appropriate thing to do is
  649.         //    to pretend the T_DISCONNECT event never happened.   Any other error is unexpected
  650.         //    and needs to be reported so we can fix it.  Next, unbind the endpoint so we can
  651.         //    reuse it for a new inbound connection.
  652.         //    
  653.         //    It is possible to get an error on the unbind due to a bug in OT 1.1.1 and earlier.
  654.         //    The best thing to do for that is close the endpoint and open a new one to replace it.
  655.         //    We do this back in the main thread so we don't have to deal with synchronization problems.
  656.         //
  657.         case T_DISCONNECT:
  658.         {
  659.             epState = OTGetEndpointState(epi->erf);
  660.             if (epState == T_OUTCON)
  661.             {
  662.                 OTAtomicAdd32(-1, &gCntrPending);
  663.             }
  664.             OTAtomicAdd32(1, &gCntrDiscon);
  665.             err = OTRcvDisconnect(epi->erf, NULL);
  666.             if (err != kOTNoError)
  667.             {
  668.                 if (err == kOTNoDisconnectErr)
  669.                     return;
  670.                 DBAlert1("Notifier: T_DISCONNECT - OTRcvDisconnect error %d", err);
  671.                 return;
  672.             }
  673.             
  674.             err = OTUnbind(epi->erf);
  675.             if (err != kOTNoError)
  676.             {
  677.                 OTLIFOEnqueue(gBrokenEPs, &epi->link);
  678.                 OTAtomicAdd32(1, &gCntrBrokenEPs);
  679.             }
  680.             return;
  681.         }
  682.         
  683.         //
  684.         //    T_GODATA:
  685.         //
  686.         //    Because of the complexity involved in the implementation of OT flow control,
  687.         //    it is sometimes possible to receive a T_GODATA even when we aren't subject 
  688.         //    to flow control - normally only at the start of a program.   If this happens,
  689.         //    ignoring it is the correct thing to do.
  690.         //
  691.         case T_GODATA:
  692.         {
  693.             return;
  694.         }
  695.         
  696.         //
  697.         //    T_OPENCOMPLETE:
  698.         //
  699.         //    This event occurs when an OTAsyncOpenEndpoint() completes.   Note that this event,
  700.         //    just like any other async call made from outside the notifier, can occur during
  701.         //    the call to OTAsyncOpenEndpoint().  That is, in the main thread the program did
  702.         //    the OTAsyncOpenEndpoint(), and the notifier is invoked before control is returned
  703.         //    to the line of code following the call to OTAsyncOpenEndpoint().   This is one
  704.         //    event we need to keep track of even if we are shutting down the program since there
  705.         //    is no way to cancel outstanding OTAsyncOpenEndpoint() calls.
  706.         //
  707.         case T_OPENCOMPLETE:
  708.         {
  709.             char serverCString[256];
  710.             
  711.             OTAtomicClearBit(&epi->stateFlags, kOpenInProgressBit);
  712.             if (result == kOTNoError)
  713.                 epi->erf = (EndpointRef) cookie;
  714.             else    
  715.             {
  716.                 DBAlert1("Notifier: T_OPENCOMPLETE result %d", result);
  717.                 return;
  718.             }
  719.  
  720.             if (gProgramState != kProgramRunning)
  721.                 return;
  722.             
  723.             if (epi == gDNS)
  724.             {
  725.                 MyP2CStr(gServerAddrStr, serverCString);
  726.                 err = OTInetStringToAddress((InetSvcRef)epi->erf, serverCString, &gServerHostInfo);
  727.                 if (err != kOTNoError)
  728.                 {
  729.                     //
  730.                     //    Can't translate the server address string
  731.                     //
  732.                     DBAlert1("Notifier: T_OPENCOMPLETE - OTInetStringToAddress error %d", err);
  733.                 }
  734.                 return;        // DNS resumes at T_DNRSTRINGTOADDRCOMPLETE
  735.             }
  736.             else
  737.             {
  738.                 OTAtomicAdd32(1, &gCntrEndpts);
  739.                 
  740.                 //
  741.                 //    Set to blocking mode so we don't have to deal with kEAGAIN errors.
  742.                 //    Async/blocking is the best mode to write an OpenTransport application in.
  743.                 //
  744.                 err = OTSetBlocking(epi->erf);
  745.                 if (err != kOTNoError)
  746.                 {
  747.                     DBAlert1("Notifier: T_OPENCOMPLETE - OTSetBlocking error %d", err);
  748.                     return;
  749.                 }
  750.                 
  751.                 DoBind(epi);
  752.                 return;            // resumes at T_BINDCOMPLETE
  753.             }
  754.         }
  755.         
  756.         //
  757.         //    T_ORDREL:
  758.         //
  759.         //    This event occurs when an orderly release has been received on the stream.
  760.         //
  761.         case T_ORDREL:
  762.         {
  763.             err = OTRcvOrderlyDisconnect(epi->erf);
  764.             if (err != kOTNoError)
  765.             {
  766.                 DBAlert1("Notifier: T_ORDREL - OTRcvOrderlyDisconnect error %d", err);
  767.                 return;
  768.             }
  769.             epState = OTGetEndpointState(epi->erf);
  770.             if (epState != T_IDLE)
  771.                 return;
  772.             OTAtomicAdd32(-1, &gCntrConnections);
  773.             err = OTUnbind(epi->erf);
  774.             if (err != kOTNoError)
  775.             {
  776.                 OTLIFOEnqueue(gBrokenEPs, &epi->link);
  777.                 OTAtomicAdd32(1, &gCntrBrokenEPs);
  778.             }
  779.             return;
  780.         }
  781.         
  782.         //
  783.         //    T_UNBINDCOMPLETE:
  784.         //
  785.         //    This event occurs on completion of an OTUnbind().
  786.         //    The endpoint is ready for reuse on a new inbound connection.
  787.         //    Put it back into the queue of idle endpoints.
  788.         //    Note that the OTLIFO structure has atomic queue and dequeue,
  789.         //    which can be helpful for synchronization protection.  
  790.         //
  791.         case T_UNBINDCOMPLETE:
  792.         {
  793.             if (result != kOTNoError)
  794.             {
  795.                 //
  796.                 //    Unbind errors can occur as a result of a bug in OT 1.1.1 and earlier
  797.                 //    versions.   The best recovery is to put the endpoint in the broken
  798.                 //    list for recycling with a clean, new endpoint.
  799.                 //
  800.                 OTLIFOEnqueue(gBrokenEPs, &epi->link);
  801.                 OTAtomicAdd32(1, &gCntrBrokenEPs);
  802.                 return;
  803.             }
  804.             DoBind(epi);
  805.             return;
  806.         }
  807.         
  808.         
  809.         //
  810.         //    default:
  811.         //
  812.         //    There are events which we don't handle, but we don't expect to see
  813.         //    any of them.   When running in debugging mode while developing a program,
  814.         //    we exit with an informational alert.   Later, in the production version
  815.         //    of the program, we ignore the event and try to keep running.
  816.         //
  817.         default:
  818.         {
  819.             DBAlert1("Notifier: unknown event <%x>", event);
  820.             return;
  821.         }
  822.     }
  823. }
  824.  
  825. //
  826. //    ReadData:
  827. //
  828. //    This routine attempts to read all available data from an endpoint.
  829. //    Since this routine is only called from inside the notifier in the current
  830. //    version of OTVirtualClient, it is not necessary to program to handle
  831. //    getting back a T_DATA notification DURING an OTRcv() call, as would be
  832. //    the case if we read from outside the notifier.   We must read until we
  833. //    get a kOTNoDataErr in order to clear the T_DATA event so we will get
  834. //    another notification of T_DATA in the future.
  835. //
  836. //    Currently this application uses no-copy receives to get data.  This obligates
  837. //    the program to return the buffers to OT asap.  Since this program does nothing
  838. //    with data other than count it, that's easy.  Future, more complex versions
  839. //    of this program will do more interesting things with regards to that.
  840. //
  841. static void ReadData(EPInfo* epi)
  842. {
  843.     OTBuffer* bp;
  844.     OTResult  res;
  845.     OTFlags      flags;
  846.     
  847.     while (true)
  848.     {
  849.         res = OTRcv(epi->erf, &bp, kOTNetbufDataIsOTBufferStar, &flags);
  850.         
  851.         if (res > 0)
  852.         {
  853.             OTAtomicAdd32(res, &gCntrBytesRcvd);
  854.             OTAtomicAdd32(res, &gCntrTotalBytesRcvd);
  855.             OTAtomicAdd32(res, &gCntrIntervalBytes);
  856.             OTReleaseBuffer(bp);
  857.             continue;
  858.         }
  859.         if (res == kOTNoDataErr)
  860.         {
  861.             //
  862.             //    Since ReadData is only called from inside the notifier
  863.             //    we don't have to worry about having missed a T_DATA 
  864.             //    during the OTRcv.
  865.             //
  866.             return;
  867.         }
  868.         if (res <= 0)
  869.         {
  870.             if (res == kOTLookErr)
  871.             {
  872.                 res = OTLook(epi->erf);
  873.                 if (res == T_ORDREL)
  874.                     return;
  875.                 if (res == T_GODATA)
  876.                 {
  877.                     //
  878.                     //    This isn't expected, but it has happened occasionally.
  879.                     //    The correct way to proceed is to ignore it.
  880.                     //
  881.                     continue; 
  882.                 }
  883.                 else
  884.                 {
  885.                     DBAlert1("ReadData: OTRcv got OTLookErr 0x%08x", res);
  886.                 }
  887.             }
  888.             else
  889.             {
  890.                 DBAlert1("ReadData: OTRcv error %d", res);
  891.             }
  892.         }
  893.     } 
  894. }
  895.  
  896. //
  897. //    Recycle:
  898. //
  899. //    This routine shouldn't be necessary, but it is helpful to work around both
  900. //    problems in OpenTransport and bugs in this program.   Basicly, whenever an
  901. //    unexpected error occurs which shouldn't be fatal to the program, the EPInfo
  902. //    is queued on the BrokenEP queue.  When recycle is called, once per pass around
  903. //    the event loop, it will attempt to close the associated endpoint and open
  904. //    a new one to replace it using the same EPInfo structure.   This process of
  905. //    closing an errant endpoint and opening a replacement is probably the most
  906. //    reliable way to make sure that this program and OpenTransport can recover
  907. //    from unexpected happenings in a clean manner.
  908. //
  909. static void Recycle()
  910. {
  911.     OTLink*     list = OTLIFOStealList(gBrokenEPs);
  912.     OTLink*        link;
  913.     EPInfo*        epi;
  914.  
  915.     while ( (link = list) != NULL )
  916.     {
  917.         list = link->fNext;
  918.         epi = OTGetLinkObject(link, EPInfo, link);
  919.         if (!EPClose(epi))
  920.         {
  921.             OTLIFOEnqueue(gBrokenEPs, &epi->link);
  922.             continue;
  923.         }
  924.         OTAtomicAdd32(-1, &gCntrBrokenEPs);
  925.         EPOpen(epi, OTCloneConfiguration(gCfgMaster));
  926.     }
  927. }
  928.  
  929. //
  930. //    SendRequest:
  931. //
  932. //    Tell the OT Virtual Server we want it to send us some data.
  933. //    For demonstration purposes, the server will wait for a 128 byte
  934. //    "request" to come in before sending us data.   It doesn't care
  935. //    what the request looks like, it just allows us to better simulate
  936. //    true client/server interactions.
  937. //
  938. static void SendRequest(EPInfo* epi)
  939. {
  940.     OTResult res;
  941.     
  942.     res = OTSnd(epi->erf, gServerRequest, kServerRequestSize, 0);
  943.     
  944.     //
  945.     //    This is bogus and needs to add flow control.
  946.     //    The only reason we get away with it here is because flow control
  947.     //    will never happen in the first 128 bytes sent, and that is all
  948.     //    we are sending.
  949.     //
  950.     if (res != kServerRequestSize)
  951.     {
  952.         DBAlert1("SendRequest: got result %d", res);
  953.     }
  954.     OTAtomicAdd32(res, &gCntrIntervalBytes);
  955. }
  956.  
  957.  
  958. //
  959. //    StartClient:
  960. //
  961. //    Open one InetServices (DNS) object,
  962. //    and as many connection endpoints as the program will use.
  963. //    Start making connections as soon as the server's name is translated
  964. //    to an IP address.
  965. //
  966. static void StartClient()
  967. {
  968.     int i;
  969.     EPInfo* epi;
  970.     OSStatus err;
  971.     
  972.     gCntrEndpts                = 0;
  973.     gCntrPending            = 0;
  974.     gCntrConnections        = 0;
  975.     gCntrBrokenEPs                = 0;
  976.     gCntrTotalConnections    = 0;
  977.     gIdleEPs->fHead            = NULL;
  978.     gBrokenEPs->fHead         = NULL;
  979.     gClientState             = kClientRunning;
  980.     TCPPrefsReset();
  981.     gWaitForServerAddr         = true;
  982.     
  983.     //
  984.     //    Open an InternetServices so we have access to the DNR
  985.     //    to translate the server's name into an IP address (if necessary).
  986.     //
  987.     gDNS = (EPInfo*) NewPtr(sizeof(EPInfo));
  988.     if (gDNS == NULL)
  989.     {
  990.         DBAlert("StartClient: NewPtr cannot get memory for EPInfo");
  991.         return;
  992.     }
  993.     OTMemzero(gDNS, sizeof(EPInfo));
  994.     OTAtomicSetBit(&gDNS->stateFlags, kOpenInProgressBit);
  995.     err = OTAsyncOpenInternetServices(kDefaultInternetServicesPath, 0, Notifier, gDNS);
  996.     if (err != kOTNoError)
  997.     {
  998.         OTAtomicClearBit(&gDNS->stateFlags, kOpenInProgressBit);
  999.         DBAlert1("OTAsyncOpenInternetServices error %d", err);
  1000.         return;
  1001.     }
  1002.     
  1003.     //
  1004.     //    Get memory for EPInfo structures
  1005.     //
  1006.     for (i = 0; i < gMaxConnections; i++)                
  1007.     {
  1008.         epi = (EPInfo*) NewPtr(sizeof(EPInfo));
  1009.         if (epi == NULL)
  1010.         {
  1011.             DBAlert("StartClient: NewPtr cannot get memory for EPInfo");
  1012.             return;
  1013.         }
  1014.         OTMemzero(epi, sizeof(EPInfo));
  1015.         epi->next = gConnectors;
  1016.         gConnectors = epi;
  1017.     }
  1018.     
  1019.     //
  1020.     //    Open endpoints which can be used for outbound 
  1021.     //    connections to the server.
  1022.     //
  1023.     gCfgMaster = OTCreateConfiguration("tcp");
  1024.     if (gCfgMaster == NULL)
  1025.     {
  1026.         DBAlert("StartClient: OTCreateConfiguration returned NULL");
  1027.         return;
  1028.     }
  1029.     for (epi = gConnectors; epi != NULL; epi = epi->next)
  1030.     {
  1031.         if (!EPOpen(epi, OTCloneConfiguration(gCfgMaster)))
  1032.             break;
  1033.     }
  1034. }
  1035.  
  1036. //
  1037. //    StopClient:
  1038. //
  1039. //    This is where the client is shut down, either because the user clicked
  1040. //    the stop button, or because the program is exiting (error or quit).
  1041. //    The tricky part is that we can't quit while there are outstanding
  1042. //    OTAsyncOpenEndpoint calls (which can't be cancelled, by the way).
  1043. //
  1044. static void StopClient()
  1045. {
  1046.     EPInfo *epi, *last;
  1047.     
  1048.     gClientState = kClientShuttingDown;
  1049.     
  1050.     //
  1051.     //    First, make sure the DNS is closed.
  1052.     //
  1053.     if (gDNS != NULL)
  1054.     {
  1055.         if (!EPClose(gDNS))
  1056.             return;
  1057.         DisposePtr((char*)gDNS);
  1058.         gDNS = NULL;
  1059.     }
  1060.     
  1061.     //
  1062.     //    Start closing connector endpoints.
  1063.     //    While we could be rude and just close the endpoints, 
  1064.     //    we try to be polite and wait for all outstanding connections
  1065.     //    to finish before closing the endpoints.   The is a bit easier
  1066.     //    on the server which won't end up keeping around control blocks
  1067.     //    for dead connections which it doesn't know are dead.  Alternately,
  1068.     //    we could just send a disconnect, but this seems cleaner.
  1069.     //
  1070.     epi = gConnectors;
  1071.     last = NULL;
  1072.     while (epi != NULL)
  1073.     {
  1074.         if (!EPClose(epi))
  1075.         {
  1076.             //    Can't close this endpoint yet, so skip it.
  1077.             last = epi;
  1078.             epi = epi->next;
  1079.             continue;
  1080.         }
  1081.         else
  1082.         {
  1083.             if (last != NULL)
  1084.             {
  1085.                 last->next = epi->next;
  1086.                 DisposePtr((char*)epi);
  1087.                 epi = last->next;
  1088.             }
  1089.             else
  1090.             {
  1091.                 gConnectors = epi->next;
  1092.                 DisposePtr((char*)epi);
  1093.                 epi = gConnectors;
  1094.             }
  1095.         }
  1096.     }
  1097.     
  1098.     //
  1099.     //    If the list is empty now, then all endpoints have been successfully closed,
  1100.     //    so the client is stopped now.   At this point we can either restart it or
  1101.     //    exit the program safely.
  1102.     //
  1103.     if (gConnectors == NULL)
  1104.     {
  1105.         gClientState             = kClientStopped;
  1106.         gCntrEndpts                = 0;
  1107.         gCntrIdleEPs                = 0;
  1108.         gCntrPending            = 0;
  1109.         gCntrConnections        = 0;
  1110.         gCntrBrokenEPs                = 0;
  1111.         gCntrTotalConnections    = 0;
  1112.         gIdleEPs->fHead            = NULL;
  1113.         gBrokenEPs->fHead         = NULL;
  1114.         OTDestroyConfiguration(gCfgMaster);
  1115.     }
  1116. }
  1117.  
  1118. //
  1119. //    TimerInit
  1120. //
  1121. //    Start up a regular timer to do housekeeping.   Strictly speaking,
  1122. //    this isn't necessary, but having a regular heartbeat allows us to
  1123. //    detect if we are so busy with network notifier processing that the
  1124. //    program's event loop isn't ever firing.   We want to know this so
  1125. //    we can at least allow the user to quit the program if they want to.
  1126. //
  1127. static void TimerInit()
  1128. {
  1129.     gTimerTask = OTCreateTimerTask(&TimerRun, 0);
  1130.     if (gTimerTask == 0)
  1131.     {
  1132.         sprintf(gProgramErr, "TimerInit: OTCreateTimerTask returned 0");
  1133.         gProgramState = kProgramError;
  1134.         return;
  1135.     }
  1136.     OTScheduleTimerTask(gTimerTask, kTimerInterval);
  1137. }
  1138.  
  1139. //
  1140. //    TimerDestroy
  1141. //
  1142. static void TimerDestroy()
  1143. {
  1144.     if (gTimerTask != 0)
  1145.     {
  1146.         OTCancelTimerTask(gTimerTask);
  1147.         OTDestroyTimerTask(gTimerTask);
  1148.         gTimerTask = 0;
  1149.     }
  1150. }
  1151.  
  1152. //
  1153. //    TimerRun
  1154. //
  1155. //    Fires every N seconds, no matter how busy the system is.
  1156. //    We use this to detect if the program's main event loop is getting no time,
  1157. //    in which case we can slow the client down by doing a throttle-back until
  1158. //    the event loop can run at least once.  It also is a convenient statistics 
  1159. //    gathering point.  
  1160. //
  1161. static pascal void TimerRun(void*)
  1162. {
  1163.     gConnectsPerSecond = (gCntrIntervalConnects / kTimerIntervalInSeconds);
  1164.     gKBytesPerSecond = (gCntrIntervalBytes / (kTimerIntervalInSeconds * 1024));
  1165.     gEventsPerSecond = (gCntrIntervalEventLoop / kTimerIntervalInSeconds);
  1166.     if (gCntrIntervalEventLoop == 0)
  1167.         gWaitForEventLoop = true;
  1168.     
  1169.     if (gConnectsPerSecond > gConnectsPerSecondMax)
  1170.         gConnectsPerSecondMax = gConnectsPerSecond;
  1171.     if (gKBytesPerSecond > gKBytesPerSecondMax)
  1172.         gKBytesPerSecondMax = gKBytesPerSecond;
  1173.     if (gEventsPerSecond > gEventsPerSecondMax)
  1174.         gEventsPerSecondMax = gEventsPerSecond;
  1175.         
  1176.     gCntrIntervalConnects    = 0;
  1177.     gCntrIntervalBytes        = 0;
  1178.     gCntrIntervalEventLoop    = 0;
  1179.     gDoWindowUpdate            = true;
  1180.     gCntrConnections         = gCntrEndpts - gCntrPending - gCntrBrokenEPs;
  1181.     
  1182.     OTScheduleTimerTask(gTimerTask, kTimerInterval);
  1183. }
  1184.  
  1185.     
  1186.  
  1187. //////////////////////////////////////////////////////////////////////////////////////
  1188. //
  1189. //    Macintosh Program Wrapper
  1190. //
  1191. //    The code from here down deals with the Macintosh environment, events,
  1192. //    menus, command keys, etc.   Networking code is in the section above.
  1193. //    Since this code is fairly basic, and since this isn't really intended
  1194. //    to be a "sample Macintosh application" (just a sample OpenTransport application)
  1195. //    this section isn't heavily commented.   There are much better Macintosh
  1196. //    application samples for handling mouse, keyboard, event loops, etc.
  1197. //
  1198. //////////////////////////////////////////////////////////////////////////////////////
  1199.  
  1200. static void AboutBox()
  1201. {
  1202.     Alert(kAboutBoxResID, NULL);
  1203. }
  1204.  
  1205. static Boolean EventDialog(EventRecord* event)
  1206. {
  1207.     DialogPtr     dp;
  1208.     short        item;
  1209.     short        itemType;
  1210.     Handle        itemHandle;
  1211.     Rect        itemRect;
  1212.     
  1213.     if (event->modifiers & cmdKey)
  1214.     {
  1215.         EventKeyDown(event);        // this allows menu commands while dialog is active window
  1216.         return false;                // note if I add cut/paste I will have to rework this.
  1217.     }
  1218.     if ((DialogSelect(event, &dp, &item)) && (dp == gDialogPtr))
  1219.     {
  1220.         GetDialogItem(gDialogPtr, item, &itemType, &itemHandle, &itemRect);
  1221.         switch (item)
  1222.         {
  1223.             case kServerAddrDItem:
  1224.                 GetDialogItemText(itemHandle, gServerAddrStr);
  1225.                 return true;
  1226.             
  1227.             case kServerPortDItem:
  1228.                 GetDialogItemText(itemHandle, gServerPortStr);
  1229.                 return true;
  1230.             
  1231.             case kMaxConnectionsDItem:
  1232.                 GetDialogItemText(itemHandle, gMaxConnectionsStr);
  1233.                 return true;
  1234.             
  1235.             case kStartStopDItem:
  1236.                 GetDialogItem(gDialogPtr, kStartStopDItem, &itemType, &itemHandle, &itemRect);
  1237.                 if (gClientRunning)
  1238.                 {
  1239.                     StopClient();                
  1240.                     SetControlTitle((ControlHandle)itemHandle, gStartStr);
  1241.                     gClientRunning = false;
  1242.                 }
  1243.                 else
  1244.                 {
  1245.                     StartClient();
  1246.                     SetControlTitle((ControlHandle)itemHandle, gStopStr);
  1247.                     gClientRunning = true;
  1248.                 }
  1249.                 DrawDialog(gDialogPtr);
  1250.                 return true;
  1251.         }
  1252.     }
  1253.     return false;
  1254. }
  1255.  
  1256. static void TCPPrefsReset()
  1257. {
  1258.     StringToNum(gServerPortStr, &gServerPort);
  1259.     StringToNum(gMaxConnectionsStr, &gMaxConnections);
  1260. }
  1261.  
  1262. static void TCPPrefsDialog()
  1263. {
  1264.     short    itemType;
  1265.     Handle    itemHandle;
  1266.     Rect    itemRect;
  1267.     
  1268.     gDialogPtr = GetNewDialog(kTCPPrefsDlogResID, NULL, kInFront);
  1269.     SetWTitle(gDialogPtr, "\pTCP Preferences");
  1270.     
  1271.     GetDialogItem(gDialogPtr, kServerAddrDItem, &itemType, &itemHandle, &itemRect);
  1272.     SetDialogItemText(itemHandle, gServerAddrStr);
  1273.     
  1274.     GetDialogItem(gDialogPtr, kServerPortDItem, &itemType, &itemHandle, &itemRect);
  1275.     SetDialogItemText(itemHandle, gServerPortStr);
  1276.     
  1277.     GetDialogItem(gDialogPtr, kMaxConnectionsDItem, &itemType, &itemHandle, &itemRect);
  1278.     SetDialogItemText(itemHandle, gMaxConnectionsStr);
  1279.     
  1280.     GetDialogItem(gDialogPtr, kStartStopDItem, &itemType, &itemHandle, &itemRect);
  1281.     if (gClientRunning)
  1282.         SetControlTitle((ControlHandle)itemHandle, gStopStr);
  1283.     else
  1284.         SetControlTitle((ControlHandle)itemHandle, gStartStr);
  1285.     DrawDialog(gDialogPtr);
  1286. }
  1287.  
  1288. static void DialogClose()
  1289. {
  1290.     DisposeDialog(gDialogPtr);
  1291.     gDialogPtr = NULL;
  1292.     TCPPrefsReset();
  1293. }
  1294.  
  1295. static void MenuDispatch(long menu)
  1296. {
  1297.     short menuID;
  1298.     short cmdID;
  1299.     
  1300.     menuID = HiWord(menu);
  1301.     cmdID  = LoWord(menu);
  1302.     switch(menuID)
  1303.     {
  1304.         case kAppleMenuResID:
  1305.         {
  1306.             switch (cmdID)
  1307.             {
  1308.                 case kAppleMenuAbout:
  1309.                     AboutBox();
  1310.                     break;
  1311.                     
  1312.                 default:
  1313.                     break;
  1314.             }
  1315.             break;
  1316.         }
  1317.             
  1318.         case kFileMenuResID:
  1319.         {
  1320.             switch (cmdID)
  1321.             {
  1322.                 case kFileMenuQuit:
  1323.                     gProgramState = kProgramDone;
  1324.                     break;
  1325.                     
  1326.                 case kFileMenuOpen:
  1327.                     WindowOpen();
  1328.                     break;
  1329.                     
  1330.                 case kFileMenuClose:
  1331.                     WindowClose();
  1332.                     break;
  1333.                     
  1334.                 default:
  1335.                     break;
  1336.             }
  1337.             break;
  1338.         }
  1339.         
  1340.         case kEditMenuResID:
  1341.             break;
  1342.         
  1343.         case kClientMenuResID:
  1344.         {
  1345.             switch (cmdID)
  1346.             {
  1347.                 case kClientMenuTCPPrefs:
  1348.                     TCPPrefsDialog();
  1349.                     break;
  1350.                     
  1351.                 default:
  1352.                     break;
  1353.             }
  1354.             break;
  1355.         }
  1356.     }
  1357.         
  1358. }
  1359.  
  1360. static void EventDrag(WindowPtr wp, Point loc)
  1361. {
  1362.     Rect dragBounds;
  1363.     
  1364.     dragBounds = qd.screenBits.bounds;
  1365.     DragWindow(wp, loc, &dragBounds);
  1366. }
  1367.  
  1368. static void EventGoAway(WindowPtr wp, Point loc)
  1369. {
  1370.     if (TrackGoAway(wp, loc))
  1371.     {
  1372.         if (wp == gWindowPtr)
  1373.             WindowClose();
  1374.         else if (wp == gDialogPtr)
  1375.             DialogClose();
  1376.     }
  1377. }
  1378.     
  1379. static void EventMouseDown(EventRecord* event)
  1380. {
  1381.     short        part;
  1382.     WindowPtr    wp;
  1383.     long         menu;
  1384.     
  1385.     part = FindWindow(event->where, &wp);
  1386.     switch (part)
  1387.     {
  1388.         case inMenuBar:
  1389.             menu = MenuSelect(event->where);
  1390.             HiliteMenu(0);
  1391.             MenuDispatch(menu);
  1392.             break;
  1393.             
  1394.         case inDrag:
  1395.             EventDrag(wp, event->where);
  1396.             break;
  1397.         
  1398.         case inGoAway:
  1399.             EventGoAway(wp, event->where);
  1400.             break;
  1401.         
  1402.         case inContent:        
  1403.             SelectWindow(wp);
  1404.             break;
  1405.             
  1406.         case inGrow:        // no grow box
  1407.         case inZoomIn:        // no zoom box
  1408.         case inZoomOut:        // no zoom box
  1409.         case inSysWindow:
  1410.         case inDesk:
  1411.         default:
  1412.             break;
  1413.     }
  1414. }
  1415.  
  1416. static void EventKeyDown(EventRecord* event)
  1417. {
  1418.     char        c;
  1419.     long        menu;
  1420.     
  1421.     c = event->message & charCodeMask;
  1422.     if (event->modifiers & cmdKey)
  1423.     {
  1424.         // cmd key
  1425.         menu = MenuKey(c);
  1426.         HiliteMenu(0);
  1427.         if (menu != 0)
  1428.             MenuDispatch(menu);
  1429.     }
  1430.     else
  1431.     {
  1432.         // normal keystroke
  1433.     }
  1434. }
  1435.  
  1436. static void EventLoop()
  1437. {
  1438.     EventRecord event;
  1439.     
  1440.     while ((gProgramState == kProgramRunning) || (gClientState != kClientStopped))
  1441.     {
  1442.         OTAtomicAdd32(1, &gCntrIntervalEventLoop);
  1443.         if (WaitNextEvent(everyEvent, &event, gSleepTicks, 0)) 
  1444.         {
  1445.             if ((gDialogPtr != NULL) && (IsDialogEvent(&event)))
  1446.             {
  1447.                 if (EventDialog(&event))
  1448.                     continue;
  1449.             }
  1450.             switch (event.what)
  1451.             {
  1452.                 case keyDown:
  1453.                     EventKeyDown(&event);
  1454.                     break;
  1455.                     
  1456.                 case mouseDown:
  1457.                     EventMouseDown(&event);
  1458.                     break;
  1459.                     
  1460.                 case updateEvt:
  1461.                     // redraw window now
  1462.                     break;
  1463.                 
  1464.                 case activateEvt:
  1465.                     // activate or deactivate window controls
  1466.                     break;
  1467.                 
  1468.                 case mouseUp:
  1469.                 case keyUp:
  1470.                 case autoKey:
  1471.                 case diskEvt:
  1472.                 case app4Evt:
  1473.                 default:
  1474.                     break;
  1475.             }
  1476.         }
  1477.         
  1478.         if (((gProgramState == kProgramRunning) && (gClientState == kClientShuttingDown)) ||
  1479.             ((gProgramState != kProgramRunning) && (gClientState != kClientStopped)))
  1480.             StopClient();
  1481.         else if ((gProgramState == kProgramRunning) && (gClientState == kClientRunning))
  1482.             NetEventLoop();
  1483.         WindowUpdate();
  1484.     }
  1485. }
  1486.  
  1487. static void WindowClose()
  1488. {
  1489.     if (gWindowPtr == NULL)
  1490.         return;
  1491.     DisposeWindow(gWindowPtr);
  1492.     gWindowPtr = NULL;
  1493. }
  1494.  
  1495. static void WindowOpen()
  1496. {
  1497.     if (gWindowPtr != NULL)
  1498.         return;
  1499.     gWindowPtr = GetNewWindow(kWindowResID, NULL, kInFront);
  1500.     SetWTitle(gWindowPtr, "\pOTVirtualClient");
  1501. }
  1502.  
  1503. static void WindowUpdate()
  1504. {
  1505.     char gStrBuf[128];
  1506.     int len;
  1507.     
  1508.     if (gWindowPtr == NULL)
  1509.         return;
  1510.  
  1511.     if (gDoWindowUpdate == false)
  1512.         return;
  1513.     gDoWindowUpdate = false;
  1514.         
  1515.     SetPort(gWindowPtr);
  1516.     EraseRgn(gWindowPtr->visRgn);
  1517.     
  1518.     gCntrConnections = gCntrEndpts - gCntrIdleEPs - gCntrPending - gCntrBrokenEPs;
  1519.     
  1520.     MoveTo(20, 20);
  1521.     sprintf(gStrBuf, "EPs: total %d idle %d", gCntrEndpts, gCntrIdleEPs);
  1522.     len = strlen(gStrBuf) ;
  1523.     DrawText(gStrBuf, 0, len);
  1524.     
  1525.     MoveTo(20, 40);
  1526.     sprintf(gStrBuf, "Connects: current %d total %d", gCntrConnections, gCntrTotalConnections);
  1527.     len = strlen(gStrBuf) ;
  1528.     DrawText(gStrBuf, 0, len);
  1529.  
  1530.     MoveTo(20, 60);
  1531.     sprintf(gStrBuf, "Pending connections %d", gCntrPending);
  1532.     len = strlen(gStrBuf) ;
  1533.     DrawText(gStrBuf, 0, len);
  1534.  
  1535.     MoveTo(20, 80);
  1536.     sprintf(gStrBuf, "KBytes received %d", (gCntrTotalBytesRcvd / 1024));
  1537.     len = strlen(gStrBuf) ;
  1538.     DrawText(gStrBuf, 0, len);
  1539.         
  1540.     MoveTo(20, 100);
  1541.     sprintf(gStrBuf, "Conn/sec: current %d max %d", gConnectsPerSecond, gConnectsPerSecondMax);
  1542.     len = strlen(gStrBuf) ;
  1543.     DrawText(gStrBuf, 0, len);
  1544.     
  1545.     MoveTo(20, 120);
  1546.     sprintf(gStrBuf, "KBy/sec: current %d max %d", gKBytesPerSecond, gKBytesPerSecondMax);
  1547.     len = strlen(gStrBuf) ;
  1548.     DrawText(gStrBuf, 0, len);
  1549.     
  1550.     MoveTo(20, 140);
  1551.     sprintf(gStrBuf, "Events/sec: %d/%d", gEventsPerSecond, gEventsPerSecondMax);
  1552.     len = strlen(gStrBuf) ;
  1553.     DrawText(gStrBuf, 0, len);
  1554.     
  1555.     MoveTo(20, 160);
  1556.     sprintf(gStrBuf, "Running at %d%% of capacity.", 
  1557.             (100 - ((100 * gEventsPerSecond)/gEventsPerSecondMax)));
  1558.     len = strlen(gStrBuf) ;
  1559.     DrawText(gStrBuf, 0, len);
  1560.     
  1561.     MoveTo(20, 180);
  1562.     sprintf(gStrBuf, "Disconnects %d", gCntrDiscon);
  1563.     len = strlen(gStrBuf) ;
  1564.     DrawText(gStrBuf, 0, len);
  1565.     
  1566. }
  1567.  
  1568.  
  1569. static void SetupMenus()
  1570. {
  1571.     MenuHandle mh;
  1572.     mh = GetMenu(kAppleMenuResID);
  1573.     AppendResMenu( mh, 'DRVR' );            /* Add DA list */
  1574.     InsertMenu(mh, 0);
  1575.     mh = GetMenu(kFileMenuResID);
  1576.     InsertMenu(mh, 0);
  1577.     mh = GetMenu(kEditMenuResID);
  1578.     InsertMenu(mh, 0);
  1579.     mh = GetMenu(kClientMenuResID);
  1580.     InsertMenu(mh, 0);
  1581.     DrawMenuBar();
  1582. }
  1583.  
  1584. static void MyC2PStr(char* cstr, Str255 pstr)
  1585. {
  1586.     //
  1587.     //    Converts a C string to a Pascal string.
  1588.     //    Truncates the string if longer than 254 bytes.
  1589.     //
  1590.     int i, j;
  1591.     
  1592.     i = strlen(cstr);
  1593.     if (i > 254)
  1594.         i = 254;
  1595.     pstr[0] = i;
  1596.     for (j = 1; j <= i; j++)
  1597.         pstr[j] = cstr[j-1];
  1598. }
  1599.  
  1600. static void MyP2CStr(Str255 pstr, char* cstr)
  1601. {
  1602.     int i;
  1603.     
  1604.     for (i = 0; i < pstr[0]; i++)
  1605.         cstr[i] = pstr[i+1];
  1606.     cstr[i] = 0;
  1607. }
  1608.  
  1609. static void AlertExit(char* err)
  1610. {
  1611.     Str255 pErr;
  1612.     
  1613.     MyC2PStr(err, pErr);
  1614.     ParamText(pErr, NULL, NULL, NULL);
  1615.     Alert(kAlertExitResID, NULL);
  1616.     ExitToShell();    
  1617. }
  1618.  
  1619. static void MacInitROM()
  1620. {
  1621.     MaxApplZone();
  1622.     MoreMasters();
  1623.     InitGraf(&qd.thePort);
  1624.     InitCursor();
  1625.     InitFonts();
  1626.     InitWindows();
  1627.     InitMenus();
  1628.     TEInit();
  1629.     InitDialogs(NULL);
  1630.     FlushEvents(everyEvent, 0);
  1631. }
  1632.  
  1633. static void MacInit()
  1634. {
  1635.     MacInitROM();
  1636.     WindowOpen();
  1637.     SetupMenus();
  1638. }
  1639.  
  1640. static void MiscInit()
  1641. {
  1642.     int i;
  1643.     
  1644.     //    This is just so the data is a little better than random for tracing
  1645.     for (i = 0; i < kServerRequestSize; i++)
  1646.         gServerRequest[i] = i;
  1647. }
  1648.  
  1649. void main()
  1650. {
  1651.     MacInit();
  1652.     NetInit();
  1653.     MiscInit();
  1654.     EventLoop();
  1655.     NetShutdown();
  1656.     if (gProgramState == kProgramError)
  1657.         AlertExit(gProgramErr);
  1658. }
  1659.